Langage interprété, orienté objet et dynamiquement typé pour
et bien d'autres domaines ...
Extensible (C/C++/Fortran ou Java)
Depuis quelques années, il est régulièrement classé comme le langage le plus populaire.
Java :
public class Hello {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}
Python 2 :
print "Hello, World!"
Python 3 :
print ("Hello, World!")
def q(L):
if len(L)<= 1: return L
return q( [x for x in L[1:] if x<L[0]] ) + [L[0]] + q( [y for y in L[1:] if y>=L[0]] )
L = [78, 46, 63, 20, 53, 10, 26, 52, 41, 54, 81, 75, 49, 21, 80, 60, 58, 56, 86,40, 95, 92, 0, 4, 77, 12, 5, 59, 90, 57, 71, 3,
65, 27, 97, 89, 19, 38, 15, 85, 6, 62, 11, 33, 67, 61, 73, 44, 50, 17, 94, 48, 43, 34, 55, 24, 87, 70, 2, 16, 42, 25, 37,
68, 88, 30, 23, 7, 83, 74, 84, 39, 32, 98, 99, 22, 1, 45, 82, 69, 96, 31, 51, 91, 14, 13, 66, 36, 9, 18, 8, 79, 47, 72, 29,
76, 93, 64, 28, 35]
print(q(L))
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99]
print(len(L)) # c'est la longueur de la liste
100
print(L[0]) # les indices commencent à 0
78
print(L[98:]) # après 98 (inclus)
[28, 35]
print(L[:3]) # avant 3 (non inclus)
[78, 46, 63]
print([x for x in L if x<5]) # Compréhension de liste
[0, 4, 3, 2, 1]
print([1,2]+[3,4]) # concaténation des listes
[1, 2, 3, 4]
Évidemment, ce n'est pas une implémentation efficace de QuickSort ! Mais ça permet de vérifier que l'algorithme donne un résultat correct.
Le tri est implémenté très efficacement en Python (TimSort - fonction sorted, ou méthode sort qui trie en place):
print(sorted(L))
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99]
L.sort()
print(L)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99]
L'utilisation la plus basique consite à entrer la commande python dans un terminal. On rentre dans un interpréteur qu'on peut utiliser comme une calculette :
[jyt@scriabine ~]$ python
Python 3.6.1 |Anaconda 4.4.0 (64-bit)| (default, May 11 2017, 13:09:58)
[GCC 4.4.7 20120313 (Red Hat 4.4.7-1)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> 2**100
1267650600228229401496703205376
On peut aussi écrire des scripts. La première ligne doit être #! suivi du chemin vers l'interpréteur:
[jyt@scriabine jyt]$ cat a.py
#!/usr/bin/python
print (2**100)
[jyt@scriabine ~]$ chmod +x a.py
[jyt@scriabine ~]$ ./a.py
1267650600228229401496703205376
On peut faire exécuter des fichiers
[jyt@scriabine ~]$ cat b.py
print(2**100)
[jyt@scriabine ~]$ python b.py
1267650600228229401496703205376
ou encore, envoyer une commande à l'interpréteur depuis le shell
[jyt@scriabine ~]$ echo "print (2**100)"|python
1267650600228229401496703205376
Avec un langage interprété, on peut tester les instructions une à une avant de les incorporer dans le programme
On aura donc un interpréteur de test dans une fenêtre, et un éditeur (vim, emacs, gedit ...) dans une autre (éviter gedit).
help(sorted)
Help on built-in function sorted in module builtins: sorted(iterable, /, *, key=None, reverse=False) Return a new list containing all items from the iterable in ascending order. A custom key function can be supplied to customize the sort order, and the reverse flag can be set to request the result in descending order.
Par exemple, on peut demander à une chaîne de caractères ce qu'elle sait faire. Le résultat est intéressant.
print (dir("toto"))
['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isascii', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'removeprefix', 'removesuffix', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']
Les attributs __xxx__
ne sont pas destinés à être utilisés directement. On peut les ignorer dans un premier temps.
help("toto".translate)
Help on built-in function translate: translate(table, /) method of builtins.str instance Replace each character in the string using the given translation table. table Translation table, which must be a mapping of Unicode ordinals to Unicode ordinals, strings, or None. The table must implement lookup/indexing via __getitem__, for instance a dictionary or list. If this operation raises LookupError, the character is left untouched. Characters mapped to None are deleted.
Pratique pour débuter : l'IDE idle, développé en pur Python à titre de démonstration, et fourni avec la distribution standard (à compiler avec tkInter, ou paquetage à installer).
Le nec plus ultra: jupyter, nouveau nom de iPython, avec son notebook. C'est ce qu'on utilise ici, et ce sera la norme pour les TP.
Il existe aussi un plugin Python (PyDev) pour Eclipse, et d'autres IDE du même genre (Eric).
Pour ce cours et les suivants (cryptographie, combinatoire), leur emploi est déconseillé : l'objectif du cours n'est pas le développement logiciel, mais la résolution de problèmes en mode interactif.
Essentiellement comme en C : indices à partir de 0, = et ==, opérateurs arithmétiques +,-,/,*,**, "guillemets" pour les chaînes, opérations bit à bit >>,<<,&,|.
Par contre, les opérateurs logiques sont or et and, et les booleens sont True et False.
a=2
b=a+1
print (a,b, a+b, a*b, a**b, a/b, a//b) # noter la différence avec python 2 : la division entière est a//b
2 3 5 6 8 0.6666666666666666 0
a<=4
True
a>5 or b>3
False
Il n'y a pas de pointeurs. Mais les variables sont des références à des objets. Si b = a, une modification de a peut affecter b :
a=2
b=a
a=3
print(b)
2
Jusqu'ici, RAS. Mais avec des objets plus complexes ...
a=[1,2,3]
b=a
a[1]=5
print(b)
[1, 5, 3]
On peut éviter ce problème avec le module copy et ses fonctions copy et deepcopy, qui permettent de réaliser des copies partielles ou totales d'un objet.
On en a rarement besoin, il suffit généralement d'avoir conscience du problème.
import
¶On accède aux objets du module avec la syntaxe module.objet
. Exemple avec copy
:
import copy
x=[1,2,3]
y=copy.copy(x)
print ('y = ', y)
x[1]=5
print ('x = ', x)
print ('y = ', y)
y = [1, 2, 3] x = [1, 5, 3] y = [1, 2, 3]
u=[1,[2,3],4]
v=copy.copy(u)
u[0]=6
print ('u = ', u)
print ('v = ', v)
u[1][0]=7
print ('u = ', u)
print ('v = ', v)
u = [6, [2, 3], 4] v = [1, [2, 3], 4] u = [6, [7, 3], 4] v = [1, [7, 3], 4]
p=[1, [2,[3,4],5], [6,7]]
q=copy.deepcopy(p)
p[1][1][0]=8
print ('p = ', p)
print ('q = ', q)
p = [1, [2, [8, 4], 5], [6, 7]] q = [1, [2, [3, 4], 5], [6, 7]]
Pas d'accolades ou de begin/end. C'est l'indentation qui détermine la structure des blocs :
for i in range(0,256): # boucle sur les entiers de 0 à 255
if chr(i).isalnum(): # chr(i) est le caractère de code i
print (i, chr(i), end=" , ") # isalnum teste s'il est alphanumérique
else: pass # = ne rien faire
# Noter la différence avec python 2 : il y a des caractères alphanumériques après 127 ...
48 0 , 49 1 , 50 2 , 51 3 , 52 4 , 53 5 , 54 6 , 55 7 , 56 8 , 57 9 , 65 A , 66 B , 67 C , 68 D , 69 E , 70 F , 71 G , 72 H , 73 I , 74 J , 75 K , 76 L , 77 M , 78 N , 79 O , 80 P , 81 Q , 82 R , 83 S , 84 T , 85 U , 86 V , 87 W , 88 X , 89 Y , 90 Z , 97 a , 98 b , 99 c , 100 d , 101 e , 102 f , 103 g , 104 h , 105 i , 106 j , 107 k , 108 l , 109 m , 110 n , 111 o , 112 p , 113 q , 114 r , 115 s , 116 t , 117 u , 118 v , 119 w , 120 x , 121 y , 122 z , 170 ª , 178 ² , 179 ³ , 181 µ , 185 ¹ , 186 º , 188 ¼ , 189 ½ , 190 ¾ , 192 À , 193 Á , 194 Â , 195 Ã , 196 Ä , 197 Å , 198 Æ , 199 Ç , 200 È , 201 É , 202 Ê , 203 Ë , 204 Ì , 205 Í , 206 Î , 207 Ï , 208 Ð , 209 Ñ , 210 Ò , 211 Ó , 212 Ô , 213 Õ , 214 Ö , 216 Ø , 217 Ù , 218 Ú , 219 Û , 220 Ü , 221 Ý , 222 Þ , 223 ß , 224 à , 225 á , 226 â , 227 ã , 228 ä , 229 å , 230 æ , 231 ç , 232 è , 233 é , 234 ê , 235 ë , 236 ì , 237 í , 238 î , 239 ï , 240 ð , 241 ñ , 242 ò , 243 ó , 244 ô , 245 õ , 246 ö , 248 ø , 249 ù , 250 ú , 251 û , 252 ü , 253 ý , 254 þ , 255 ÿ ,
Le point-virgule en fin de ligne est optionnel. On peut l'utiliser pour mettre plusieurs instructions par ligne :
x='abra'; y='cadabra'; z=x+y; print (z)
abracadabra
On peut utiliser des \ pour continuer une instruction sur la ligne suivante :
z ='abra' \
+'cadabra'
print(z)
abracadabra
Il est parfois plus simple (et recommandé) d'utiliser des parenthèses :
z = ('abra'
+ "cadabra")
print(z)
abracadabra
On ne doit pas mélanger espaces et tabulations. La convention la plus répandue est d'indenter de 4 espaces.
if condition:
faire_si_vrai()
elif autre_condition:
faire_autre_truc()
else:
faire_si_tout_faux()
while condition:
iteration()
if fini: break # sort de la boucle
if pouet: continue # saute ce qui suit
autre_chose()
condition n'est pas forcément un booléen True/False.
On pourra donc écrire simplement
if i au lieu de
if i != 0.
Les opérateurs booléens s'écrivent or, and, not.
Le test d'égalité est a == b, et sa négation a != b.
Sémantique :
Par exemple au lieu de
if a !=0: x = 666/a
else: x = 42
on peut écrire
x = (a and 666/a) or 42
On peut aussi combiner test et affectation :
a = 0
x = 5*a if a>0 else 42 # equivalent de x = (a>0) ? (5*a:42)
x
42
On peut donc finalement écrire quicksort sur une seule ligne !
def q(L): return q([x for x in L[1:] if x<L[0]])+[L[0]]+q([y for y in L[1:] if y>=L[0]]) if len(L)>1 else L
print (q(L))
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99]
Les itérations portent sur des listes, ou plus généralement sur des objets itérables : une seule syntaxe, for i in quelquechose:
for element in liste:
manip(element)
if foo(element): break
if quux(element): continue
chose(element)
Pour itérer sur des entiers on engendre un itérateur sur leur liste au moyen de la fonction range
Syntaxe : range([debut=0],fin,[pas=1]
print (range(2,10)) # en python 3 c'est un itérateur
print (list(range(2,10)))
print (list(range(10)))
print (list(range(0,10,2)))
print (list(range(10,0,-1)))
range(2, 10) [2, 3, 4, 5, 6, 7, 8, 9] [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] [0, 2, 4, 6, 8] [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
for i in range(10):
print (2*i+1, end = ', ')
1, 3, 5, 7, 9, 11, 13, 15, 17, 19,
>>> 2+3*4
14
>>> 1<<8 # bit shift, aussi >>, &, |, ^ (XOR)
256
>>> 5**3 # puissance 3
125
>>> 42%10 # modulo 10
2
>>> 2**(1/12)
1.0594630943592953
>>> 2**200 # entiers arbitrairement longs
1606938044258990275541962092341162602522202993782792835301376L
On voit que Python supporte la multiprécision (utile pour la cryptographie, entre autres).
Les fonctions mathématiques de base sont dans le module math :
>>> import math
>>> math.cos(math.pi/3)
0.50000000000000011
On dispose de fonctions de conversion entre bases
>>> int("1101101",2)
109
>>> hex(_)
'0x6d'
>>> int(_,16) # la variable _ contient le dernier résultat calculé
109
>>> bin(109)
'0b1101101'
0xff # hexadécimal
255
0o44 # octal
36
0b1101 # binaire
13
Pour les flottants en multiprécision, c'est le module decimal qu'il faut utiliser. On s'en servira occasionnellement dans les TD de cryptographie.
from decimal import *
getcontext()
Context(prec=28, rounding=ROUND_HALF_EVEN, Emin=-999999, Emax=999999, capitals=1, clamp=0, flags=[], traps=[InvalidOperation, DivisionByZero, Overflow])
getcontext().prec = 200
r = Decimal(2).sqrt() # racine carrée de 2
print(r)
1.4142135623730950488016887242096980785696718753769480731766797379907324784621070388503875343276415727350138462309122970249248360558507372126441214970999358314132226659275055927557999505011527820605715
r
Decimal('1.4142135623730950488016887242096980785696718753769480731766797379907324784621070388503875343276415727350138462309122970249248360558507372126441214970999358314132226659275055927557999505011527820605715')
On peut utiliser deux types de guillemets. 'toto' et "toto" sont équivalentes.
Permet d'écrire "l'apostrophe" ou 'les "guillemets"'.
Les triples guillemets """...""" ou '''...''' permettent d'écrire sur plusieurs lignes.
Une chaîne qui traîne au milieu d'un programme est vue comme un commentaire.
Placées aux endroits idoines, elles permettent de générer automatiquement la documentation. Les autres commentaires commencent par un #.
a = 'toto'; b = "toto"
a == b
True
def nothing():
'''Do nothing.
Usage: nothing()'''
pass
help(nothing)
Help on function nothing in module __main__: nothing() Do nothing. Usage: nothing()
L'opérateur de concaténation est le +, on peut aussi répéter une chaîne en la multipliant par un entier :
'abra'+"""cadabra"""
'abracadabra'
'bla'*3
'blablabla'
Le contenu des chaînes est accessible via une syntaxe de type tableau :
s = 'shaviro rotantacha shamipataro robrulapatacha'
s[1]
'h'
print (s[7:15]) # une tranche
print (s[::2]) # un caractère sur deux (:: signifie que début et fin sont les valeurs par défaut)
print (s[::-1]) # retournée
print (s[19:3:-2]) # de 19 à 2 par pas de 2
print (s[-1]) # indices circulaires : -1 est le dernier
rotanta saiortnah hmptr orlptca ahcatapalurbor oratapimahs ahcatnator orivahs sactao r a
A la différence des listes/tableaux, les chaînes sont immuables :
s[0] = 'b'
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) /tmp/ipykernel_7671/4055836882.py in <module> ----> 1 s[0] = 'b' TypeError: 'str' object does not support item assignment
On dispose de nombreux outils pour construire des chaînes :
"abc%sdef%d" % ("BLA",4) # formatage style "printf"
'abcBLAdef4'
"abc{0:s}sdef{1:d}".format("BLA",4) # nouvelle syntaxe
'abcBLAsdef4'
"%08X" % 42069 # conversions de base (ici, hexa sur 8 caractères avec lettres majuscules)
'0000A455'
"%%%s" % "d" % 12 # On peut aussi écrire du code illisible (exercice : comprendre le résultat)
'12'
Les chaines ont de nombreuses méthodes très pratiques (essayer dir('')).
print ("rac" in "abracadabra") # équivaut à 'abracadabra'.__contains__('rac'))
print (len("toto")) # équivaut à 'toto'.__len__())
print ("toto".upper())
print (" \t x yz \n".strip())
print ("albert".startswith("al"))
print ("salamalec".replace("al","ila"))
print ("l'an de l'ananas".count("an"))
True 4 TOTO x yz True silaamilaec 3
La plupart des objets peuvent être convertis en strings de deux manières. str(obj) est la chaîne affichée par print obj, et repr(obj) est celle affichée par l'interpréteur quand on entre obj. Si possible, repr renvoie une chaîne qui peut être reconvertie en objet par la fonction eval.
import datetime
today = datetime.datetime.now()
str(today)
'2023-09-11 08:53:29.254456'
repr(today)
'datetime.datetime(2023, 9, 11, 8, 53, 29, 254456)'
print(eval(repr(today)))
2023-09-11 08:53:29.254456
Le module string offre quelques fonctionalités supplémentaires (pour la plupart obsolètes, voir cependant maketrans et translate, ainsi que les chaines punctuation, whitespace, ...).
La gestion des encodages a été une des pricipales motivations pour python 3.
s = '我的表弟的马只在周日吃了干草' # copié-collé depuis une page en utf-8
s
'我的表弟的马只在周日吃了干草'
s.decode('utf8') # toutes les chaînes sont en unicode
--------------------------------------------------------------------------- AttributeError Traceback (most recent call last) /tmp/ipykernel_7671/3756284376.py in <module> ----> 1 s.decode('utf8') # toutes les chaînes sont en unicode AttributeError: 'str' object has no attribute 'decode'
s.encode('utf8') # on peut donc les réencoder directement
b'\xe6\x88\x91\xe7\x9a\x84\xe8\xa1\xa8\xe5\xbc\x9f\xe7\x9a\x84\xe9\xa9\xac\xe5\x8f\xaa\xe5\x9c\xa8\xe5\x91\xa8\xe6\x97\xa5\xe5\x90\x83\xe4\xba\x86\xe5\xb9\xb2\xe8\x8d\x89'
_.decode('utf8') # 'b' signifie 'bytes'
'我的表弟的马只在周日吃了干草'
La méthode decode
ne gère que les encodages de caractères. Pour les encodages de données binaires comme 'hex', 'base64', 'uu', etc. il faut utiliser le module codecs
.
import codecs # là encore, c'est différent du python 2
codecs.encode(s.encode('utf8'),'base64')
b'5oiR55qE6KGo5byf55qE6ams5Y+q5Zyo5ZGo5pel5ZCD5LqG5bmy6I2J\n'
codecs.encode(s.encode('utf8'),'hex')
b'e68891e79a84e8a1a8e5bc9fe79a84e9a9ace58faae59ca8e591a8e697a5e59083e4ba86e5b9b2e88d89'
s[0]
'我'
ll = ['toto', 42, ['ga', 'bu', 'zo', 'meu'], 999] # Une liste peut contenir n'importe quoi
print (ll[0])
print (ll[2])
toto ['ga', 'bu', 'zo', 'meu']
print (ll[2][-1])
meu
ll.append('foo') # la méthode append insère un élément à la fin
ll
['toto', 42, ['ga', 'bu', 'zo', 'meu'], 999, 'foo']
ll.pop() # et pop() le dépile
'foo'
ll
['toto', 42, ['ga', 'bu', 'zo', 'meu'], 999]
mm = [1,2,3]; nn = [4,5,6] # concaténation avec +
mm+nn
[1, 2, 3, 4, 5, 6]
mm*3 # répétition
[1, 2, 3, 1, 2, 3, 1, 2, 3]
mm.extend(nn) # plus efficace
mm
[1, 2, 3, 4, 5, 6]
42 in ll # test de présence
True
ll[-1] = 'bla' # les listes sont mutables
ll
['toto', 42, ['ga', 'bu', 'zo', 'meu'], 'bla']
ll[2].sort() # même à l'intérieur d'un objet
ll
['toto', 42, ['bu', 'ga', 'meu', 'zo'], 'bla']
del(ll[2]) # on peut détruire n'importe quelle entrée
ll
['toto', 42, 'bla']
ll.reverse() # renverser la liste en place
ll
['bla', 42, 'toto']
s = 'abracadabra' # Les chaînes peuvent être converties en listes de caractères
list(s)
['a', 'b', 'r', 'a', 'c', 'a', 'd', 'a', 'b', 'r', 'a']
' '.join(_) # méthode join de la chaine ' ' (espace)
'a b r a c a d a b r a'
t = 'Il est plus facile de se laver les dents dans un verre à pied que de se laver les pieds dans un verre à dents'
tt = t.split() # par défaut, coupe selon les espaces et autres blancs
print (tt)
['Il', 'est', 'plus', 'facile', 'de', 'se', 'laver', 'les', 'dents', 'dans', 'un', 'verre', 'à', 'pied', 'que', 'de', 'se', 'laver', 'les', 'pieds', 'dans', 'un', 'verre', 'à', 'dents']
Les tuples sont comme des listes, mais immuables. Ils sont délimités par des parenthèses.
Le tuple vide est (), celui à un élément x est (x,). La virgule crée automatiquement un tuple.
t = 1,2,3
t
(1, 2, 3)
t[0]=7
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-131-a1f3206a315a> in <module>() ----> 1 t[0]=7 TypeError: 'tuple' object does not support item assignment
tuple('abracadabra')
('a', 'b', 'r', 'a', 'c', 'a', 'd', 'a', 'b', 'r', 'a')
vide =()
vide
()
singleton=(42,)
singleton
(42,)
(42)
42
list(vide)
[]
list(singleton)
[42]
a = 2; b= 7
print (a,b)
a,b = b,a # echange en une instruction
print (a,b)
2 7 7 2
Les dictionnaires sont des tables de hachage, implémentées de manière très efficace.
Un dictionnaire est défini par des couples clef:valeur entre accolades.
Pour pouvoir être hachées, les clefs doivent être immuables : entiers, chaînes, tuples ...
bazar = {"e": 2.71828, "jaune": (255,255,0), "vrai": True, 10: "dix", "liste": [4, 2, {False: "faux"}], (0,0):"origine"}
bazar["liste"]
[4, 2, {False: 'faux'}]
bazar.keys() # noter la différence avec python 2
dict_keys(['e', 'jaune', 'vrai', 10, 'liste', (0, 0)])
type(_)
dict_keys
bazar.items()
dict_items([('e', 2.71828), ('jaune', (255, 255, 0)), ('vrai', True), (10, 'dix'), ('liste', [4, 2, {False: 'faux'}]), ((0, 0), 'origine')])
d = dict([(1,"un"), (2,"deux")]) # On peut créer un dictionnaire avec une liste de couples
d
{1: 'un', 2: 'deux'}
for k in d: print (k, end = ' ') # on itère sur les clefs
1 2
del bazar["liste"]
print (bazar)
{'e': 2.71828, 'jaune': (255, 255, 0), 'vrai': True, 10: 'dix', (0, 0): 'origine'}
fiche = {'nom':'Toto', 'age':7} # On peut utiliser un dictionnaire pour formater avec %
"Je m'appelle %(nom)s et j'ai %(age)d ans" % fiche
"Je m'appelle Toto et j'ai 7 ans"
s = "le cheval de mon cousin ne mange du foin que le dimanche"
# Pour compter le nombre d'occurences de chaque caractère (et on peut faire encore plus compact)
d = {}
for c in s:
if c in d: d[c] +=1
else: d[c] = 1
print (d)
{'l': 3, 'e': 8, ' ': 11, 'c': 3, 'h': 2, 'v': 1, 'a': 3, 'd': 3, 'm': 3, 'o': 3, 'n': 6, 'u': 3, 's': 1, 'i': 3, 'g': 1, 'f': 1, 'q': 1}
# La version plus compacte nous amène aux sujets suivants
print ({c:s.count(c) for c in set(s)})
{'v': 1, 'd': 3, 's': 1, 'u': 3, 'a': 3, 'i': 3, 'f': 1, 'h': 2, 'o': 3, 'e': 8, 'm': 3, 'l': 3, ' ': 11, 'n': 6, 'g': 1, 'c': 3, 'q': 1}
Un ensemble, c'est un dictionnaire dont les clefs n'ont pas de valeur :
E = {2,3,5,7,11,13}
F = {5,7,17,19,23}
E.union(F)
{2, 3, 5, 7, 11, 13, 17, 19, 23}
E.intersection(F)
{5, 7}
F.difference(E)
{17, 19, 23}
print (set(s))
{'v', 'd', 's', 'u', 'a', 'i', 'f', 'h', 'o', 'e', 'm', 'l', ' ', 'n', 'g', 'c', 'q'}
C'est une syntaxe très expressive, empruntée à Haskell. L'idée est de construire une liste et écrivant entre crochets la description de ce qu'on veut y mettre :
[x for x in truc if bidule(x)]
print ([x for x in s if x in 'aeiouy'])
['e', 'e', 'a', 'e', 'o', 'o', 'u', 'i', 'e', 'a', 'e', 'u', 'o', 'i', 'u', 'e', 'e', 'i', 'a', 'e']
print ({x:s.count(x) for x in set(s)})
{'v': 1, 'd': 3, 's': 1, 'u': 3, 'a': 3, 'i': 3, 'f': 1, 'h': 2, 'o': 3, 'e': 8, 'm': 3, 'l': 3, ' ': 11, 'n': 6, 'g': 1, 'c': 3, 'q': 1}
print ([x for x in range(0,256) if chr(x).isdigit()])
[48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 178, 179, 185]
# Crible d'Eratosthène
noprimes = [j for i in range(2, 8) for j in range(i*2, 50, i)]
primes = [x for x in range(2, 50) if x not in noprimes]
primes
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47]
print (noprimes)
[4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, 36, 39, 42, 45, 48, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 10, 15, 20, 25, 30, 35, 40, 45, 12, 18, 24, 30, 36, 42, 48, 14, 21, 28, 35, 42, 49]
# Extraire les variables affectées dans un fichier de configuration
d=dict([kv.split("=",1)
for kv in open("/etc/libuser.conf").read().split("\n")
if "=" in kv and not kv.strip().startswith("#")])
d
{'LU_GIDNUMBER ': ' %u', 'LU_GROUPNAME ': ' %n', 'LU_USERNAME ': ' %n', 'create_modules ': ' files shadow', 'crypt_style ': ' blowfish', 'default_useradd ': ' /etc/default/useradd', 'login_defs ': ' /etc/login.defs', 'modules ': ' files shadow'}
On définit une fonction par le mot clef def, suivi du nom de la fonction, des arguments entre parenthèses, et de ":". Si la fonction doit renvoyer une valeur, il faut utiliser return
def f(x,y):
return 2*x+y
print (f(2,5))
print (f('bla','blu'))
9 blablablu
# Fonction récursive
def factorielle(n):
if n<2: return 1
return n*factorielle(n-1)
factorielle(100)
93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000
def aire(x1,y1,x2,y2):
delta_x=x2-x1
delta_y=y2-y1
return abs(delta_x*delta_y)
aire(1,2,6,8)
30
Les fonctions peuvent avoir des argument nommés avec des valeurs par défaut :
def aire(a,b,x2=None,y2=None):
if x2: x1,y1=a,b
else: (x1,y1),(x2,y2)=a,b
return abs((x2-x1)*(y2-y1))
print (aire(1,2,6,8))
print (aire((1,2), (6,8)))
30 30
Interdit : fonction(param=valeur,autreparam)
On peut avoir un nombre variable d'arguments :
def printf(format_string, *args):
print (format_string % args)
printf("%s: %d","trois",3)
liste=["riri", "fifi", "loulou", 17]
printf("%s + %s + %s = %d",*liste)
trois: 3 riri + fifi + loulou = 17
L'opérateur * extrait les éléments d'une liste (seulement dans un appel de fonction).
toto(a,b,c) = toto(*[a,b,c])
On peut aussi avoir des argument nommés arbitraires :
def apply_font(font="Helvetica", size=12, **styles):
print ("Font name is %s."%font)
print ("Font size is %d."%size)
print ("Extra attributes:")
for attrname,attrvalue in styles.items():
print ("\t%s: %s"%(attrname,str(attrvalue)))
apply_font(size=24,color="red",background="black")
Font name is Helvetica. Font size is 24. Extra attributes: color: red background: black
La variable styles est vue comme un dictionnaire, et l'opérateur ** en extrait les couples clef=valeur.
La forme la plus générale d'une fonction est :
def f(x,y,*ll, **dd):
print(x,y)
print(ll)
print(dd)
for a in dd: print(a,dd[a], end = ' ')
f(1,2, 3,4, cinq=5, six=6)
1 2 (3, 4) {'cinq': 5, 'six': 6} cinq 5 six 6
Une fonction peut renvoyer plusieurs valeurs. Il suffit de retourner un tuple. Ses éléments peuvent alors être affectées en une seule instruction à différentes variables :
def division(a,b):
return a//b, a%b
q,r = division (26,3)
print (q,r)
8 2
Lorsqu'on passe un paramètre à une fonction, une référence à l'objet passé est ajoutée à la portée locale de la fonction.
Lorsqu'on modifie l'objet en place, la fonction voit les modifications, car le paramètre local est une référence à l'objet qu'elle voit.
Lorsque l'on affecte une nouvelle valeur au paramètre local, l'objet ne change pas. Le nom du paramètre est maintenant une référence à un objet différent, mais cela n'affecte pas le contenu de l'objet d'origine.
def f(a, b, c):
a += "!!!"
b.append(5)
c = []
txt = "???"
list1 = [1, 2, 3]
list2 = [1.0, 2.0]
f(txt, list1, list2)
print ("txt =", txt)
print ("list1 =", list1)
print ("list2 =", list2)
txt = ??? list1 = [1, 2, 3, 5] list2 = [1.0, 2.0]
Lorsque la fonction est invoquée, elle crée trois noms locaux qui contiennent des références aux objets passés passés: a = txt, b = list1, c = list2
La ligne a + = "!!!" attribue une nouvelle référence d'objet au nom local a. Elle ne change pas la chaîne in situ. (Les chaînes ne peuvent pas être modifiées en tout parce qu'elles sont des objets immuables.
La ligne b.append (5) ajoute une valeur à l'objet pointé par b. C'est le même objet que f connaît comme list1.
La ligne c = [] attribue une nouvelle référence d'objet au nom local c. Elle ne modifie pas l'objet c mentionné précédemment
Il est inutile de donner un nom à une fonction qu'on n'utilise qu'une fois. Le mot clef lambda (emprunté au Lisp, basé sur le lambda-calcul) permet de définir une fonction anonyme :
lambda x: x*x
<function __main__.<lambda>(x)>
f = lambda x:x*x
f(3)
9
Elles s'utilisent pricipalement avec les constructions map,filter,reduce, issues de la programmation fonctionnelle :
map(lambda x: x**2, range(1,10)) # liste des carrés des entiers de 1 à 9
<map at 0x7ffb0c11aeb0>
# map retourne un itérateur en python 3
list(_)
[1, 4, 9, 16, 25, 36, 49, 64, 81]
filter(lambda x: x%2, range(1,10)) # sélectionne les nombres impairs (x%2=0, qui est ""faux" si x est pair)
<filter at 0x7ffb0c114d60>
list(_)
[1, 3, 5, 7, 9]
from functools import reduce # il faut aller le chercher dans functools
reduce(lambda x,y: x*y, range(1,101)) # calcule 100!
93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000
from operator import mul
reduce(mul, range(1,101)) # autre version
93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000
reduce(f(x,y),liste) applique f aux deux premiers éléments, puis au résultat et au troisième, et ainsi de suite...
Ces constructions sont dépréciées. map(f,ll) peut être avantageusement remplacée par
[f(x) for x in ll]. Les deux autres sont reléguées au module functools en python 3. Elles sont considérées comme peu lisibles, mais restent commodes quand on travaille en mode interprété.
Typage dynamique : les types sont implicitement polymorphes
On peut surcharger les opérateurs. Par exemple, + est automatiquement défini sur les objets qui possèdent une
méthode spéciale
`python
__add__
Duck typing : Connaître le type d'un objet n'a pas d'importance, il faut seulement s'assurer qu'on peut lui appliquer les traitements souhaités
"si ça marche comme un canard et si ça cancane comme un canard, alors ce doit être un canard (et si c'est une oie, on doit pouvoir faire avec)".
Utiliser la coertion si un objet doit être d'un type particulier str(x) plutôt que de demander isinstance(x,str).
EAFP : "It's easier to ask forgiveness than permission". Utiliser le mécanisme de gestion des exceptions (try ... except) pour déterminer le traitement approprié.
Les version récentes de Python permettent les annotations de type :
def longueur(x: str) -> int:
return len(x)
longueur('abracadabra')
11
longueur(42)
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) /tmp/ipykernel_5730/3956641685.py in <module> ----> 1 longueur(42) /tmp/ipykernel_5730/1157011441.py in longueur(x) 1 def longueur(x: str) -> int: ----> 2 return len(x) TypeError: object of type 'int' has no len()
Nous ne les utiliserons pas. Elles sont utiles pour éviter les bugs, mais il est plus facile de commencer par utiliser le typage dynamique, on peut ensuite rajouter les types et refaire des tests avant de mettre un programme en production. Noter aussi que l'annotation de fonctions est optionnelle : même si lors de l'appel de la fonction, le paramètre n'est pas du même type que celui inscrit dans la définition, Python ne générera pas d'erreurs.